((win) => {
    let isEmptyObject = function(obj) {
            for (var name in obj) {
                return false;
            }
            return true;
        },
        isFunction = function(obj) {
            return typeof obj === "function" && typeof obj.nodeType !== "number";
        },
        extend = function(t, p, f) {
            t = t || {};
            let toStr = Object.prototype.toString,
                arrStr = "[object Array]";
            for (var n in p) {
                if (f && f(n)) {
                    continue;
                }
                if (p.hasOwnProperty(n)) {
                    if (p[n] !== "null" && typeof p[n] == "object") {
                        t[n] = toStr.call(p[n]) == arrStr ? [] : {}; //代替
                        extend(t[n], p[n]);
                    } else {
                        if (t[n] && typeof t[n].type !== "undefined") {
                            if (p[n].constructor === t[n].type) {
                                t[n] = p[n];
                            } else {
                                t[n] = t[n].default();
                            }
                        } else {
                            t[n] = p[n];
                        }
                    }
                }
            }
            return t;
        },
        hasOwn = {}.hasOwnProperty,
        isPlainObject = function(obj) {
            var key;
            if (!obj || typeof obj !== "object" || obj.nodeType) {
                return false;
            }
            try {
                if (
                    obj.constructor &&
                    !hasOwn.call(obj, "constructor") &&
                    !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")
                ) {
                    return false;
                }
            } catch (e) {
                return false;
            }
            for (key in obj) {}
            return key === undefined || hasOwn.call(obj, key);
        };
    class DOM {
        constructor() {
            this.vdom = {};
            this.ComponentList = {};
            return this;
        }
        jsonToDom(json) {
            let that = this;
            let loopAppend = function(json) {
                let dom = null;
                for (let n in json) {
                    let args = [
                        n,
                        extend({}, json[n], function(a) {
                            return a === "childNodes";
                        }),
                    ];
                    if (json[n].childNodes) {
                        for (let i = 0; i < json[n].childNodes.length; i++)
                            args.push(
                                isPlainObject(json[n].childNodes[i]) ?
                                loopAppend(json[n].childNodes[i]) :
                                json[n].childNodes[i]
                            );
                    }
                    dom = that.createDom.apply(that, args);
                }
                return dom;
            };
            return loopAppend(json);
        }
        setVdom() {
            //console.log(this);
            let args = arguments;
            let dom = {};
            for (let i = 0; i < args.length; i++) {
                switch (i) {
                    case 0:
                        dom[args[0]] = {};
                        break;
                    case 1:
                        dom[args[0]] = args[1];
                        extend(dom[args[0]], {
                            childNodes: [],
                        });
                        break;
                    default:
                        dom[args[0]].childNodes.push(
                            this.ComponentList[args[i]] ?
                            this.createVdom(this.ComponentList[args[i]].template, args[i]) :
                            args[i]
                        );
                        break;
                }
            }
            return dom;
        }
        createDom() {
            let args = arguments;
            let dom = null;
            let that = this;
            for (let i = 0; i < args.length; i++) {
                switch (i) {
                    case 0:
                        if (args[0] === "text") {
                            dom = document.createTextNode(args[1].text || "");
                        } else {
                            dom = document.createElement(args[0]);
                        }
                        break;
                    case 1:
                        if (args[0] !== "text") {
                            that.createAttrs(dom, args[1]);
                        }
                        break;
                    default:
                        if (args[i].template && args[i].name) {
                            dom.appendChild(args[i].template(this));
                        } else if (typeof args[i] === "string") {
                            if (this.ComponentList[args[i]]) {
                                dom = newLitHTML.render(args[i], {}, dom);
                            } else {
                                dom.appendChild(document.createTextNode(args[i]));
                            }
                        } else {
                            dom.appendChild(args[i]);
                        }
                        break;
                }
            }
            //this.createVdom(args[0], args[1], dom);
            return dom;
        }
        createAttrs(dom, attrs) {
            if (isEmptyObject(attrs)) {
                return this;
            }
            for (let n in attrs) {
                if (dom) {
                    if (n === "text") {
                        if (dom.nodeType === 3) {
                            dom.innerHTML = attrs[n];
                        } else {
                            let text = document.createTextNode(attrs[n]);
                            dom.appendChild(text);
                        }
                    } else if (n === "html") {
                        dom.innerHTML = attrs[n];
                    } else if (/^on/.test(n)) {
                        dom.addEventListener(n.replace(/on/gim, ""), attrs[n]);
                    } else {
                        if (isPlainObject(attrs[n])) {
                            let styles = [];
                            for (let a in attrs[n]) {
                                styles.push(a + ":" + attrs[n][a]);
                            }
                            dom.setAttribute(n, styles.join(";"));
                        } else {
                            dom.setAttribute(n, attrs[n]);
                        }
                    }
                }
            }
            return this;
        }
        createVdom(template, name) {
            let that = this;
            if (isFunction(template)) {
                template = template.call(that.ComponentList[name], function() {
                    return that.setVdom.apply(that, arguments);
                });
                return template;
            }
        }
    }

    class litHTML {
        constructor( /* template, value */ ) {
            this.DOM = new DOM();

            return this;
        }
        html(template, value) {
            let vdom = extend({}, this.DOM.ComponentList[template].vdom);
            let replaceText = function(svdom, value) {
                for (let n in svdom) {
                    "text,innerHTML,outerHTML,value".split(",").forEach((v) => {
                        if (svdom[n][v]) {
                            svdom[n][v] = svdom[n][v].replace(
                                /\${([^}{]+)}/gim,
                                function(a, b) {
                                    a =
                                        isPlainObject(value[b]) && value[b].type ?
                                        value[b].default() :
                                        value[b];
                                    return a;
                                }
                            );
                        }
                    });
                    if (svdom[n].childNodes && svdom[n].childNodes.length > 0) {
                        let len = svdom[n].childNodes.length;
                        for (let i = 0; i < len; i++)
                            svdom[n].childNodes[i] = replaceText(
                                svdom[n].childNodes[i],
                                value
                            );
                    }
                }
                return svdom;
            };
            vdom = replaceText(vdom, value);
            return this.DOM.jsonToDom(vdom);
        }
        render(template, value, parent, noAppend) {
            let args = arguments,
                len = args.length,
                isComponent = false,
                that = this;
            switch (len) {
                case 3:
                case 4:
                    if (this.DOM.ComponentList[args[0]]) {
                        value = extend(
                            extend(extend({}, this.DOM.ComponentList[args[0]].props), value),
                            this.DOM.ComponentList[args[0]].data
                        );
                        isComponent = true;
                    }
                    parent = args[2];
                    noAppend = args[3] || false;
                    break;
                default:
                    return this;
            }

            if (isComponent && that.DOM.ComponentList[args[0]].created) {
                let bool = that.DOM.ComponentList[args[0]].created();
                if (bool === false) return false;
            }
            let f = document.createDocumentFragment(),
                html = that.html(template, value);
            f.appendChild(html);
            if (noAppend) {
                [].slice.call(parent.childNodes).forEach((t) => {
                    parent.removeChild(t);
                });
            }
            parent.appendChild(f);
            if (isComponent && that.DOM.ComponentList[args[0]].mounted) {
                let bool = that.DOM.ComponentList[args[0]].mounted();
                if (bool === false) return false;
            }
            return parent;
        }
        Component(name, options) {
            let component = {
                name: name,
                template: options.template,
                data: isFunction(options.data) ? options.data() : options.data,
                props: options.props || {},
                created: options.created || false,
                mounted: options.mounted || false,
                methods: options.methods || {},
                //source: options,
            };
            this.DOM.ComponentList[name] = component;
            this.DOM.ComponentList[name].vdom = this.DOM.createVdom(
                this.DOM.ComponentList[name].template,
                name
            );
            return component;
        }
    }

    let newLitHTML = (win.$NLH = new litHTML());

    define(function() {
        return newLitHTML;
    });
})(window);